DataCenter = function (seed) {
	this.seed = seed ? seed : 1234;
	this.reset();
};

DataCenter.prototype = {
	reset: function () {
		if (this.sim) delete this.sim;

		for (var name in this.racks) {
			delete this.racks[name];
		}

		for (var name in this.switches) {
			delete this.switches[name];
		}

		for (var name in this.disks) {
			delete this.disks[name];
		}

		for (var name in this.hosts) {
			delete this.hosts[name];
		}

		this.racks = {};
		this.switches = {};
		this.disks = {};
		this.hosts = {};

		this.sim = new Sim();
		//this.sim.setLogger(print);

		this.random = new Random(this.seed);
	},
	
	load: function (conf) {
		this.reset();

		// Make sure the top level entries are Arrays
		if (conf.racks && !(conf.racks instanceof Array)) {
			throw new Error("racks must be array");
		}


		/** Read racks */
		if (!conf.racks || conf.racks.length == 0) {
			// if none defined.. assume one rack
			this.racks['default_rack'] = {name: 'default_rack', hosts: []};
		} else {
			for (var i = 0; i < conf.racks.length; i++) {
				var rack = conf.racks[i];
				this.racks[rack.name] = {name: rack.name, hosts: []};
			}
		}

		/** Read Switches */
		var connections = [];
		for (var i = 0; i < conf.switches.length; i ++) {
			var sw = conf.switches[i];

			if (!sw.name) {
				throw new Error("Name not given for switch ");
			}

			var name = sw.name;

			if (this.switches[name]) {
				throw new Error("A switch by name '" + name + "' already exists.");
			}

			if (!sw.ports) {
				throw new Error("Number of ports not defined for switch " + name);
			}

			if (!sw.speed) {
				throw new Error("Speed not defined for switch " + name);
			}

			this.switches[name] = this.sim.addEntity(Switch);
			this.switches[name].init(sw.ports, sw.speed, name);

			if (sw.connect) {
				var connects;
				if (sw.connect instanceof Array) {
					connects = sw.connect;
				} else {
					connects = [sw.connect];
				}

				for (var j = 0; j < connects.length; j++) {
					connections.push([name, connects[j]]);
				}
			}
		}

		/** Connect switches together */
		var already_connected = {};
		for (var i = 0; i < connections.length; i ++) {
			var sw = connections[i][0];
			var othersw = connections[i][1];

			if (!this.switches[othersw]) {
				throw new Error("Switch " + sw + " cannot connect to " 
						+ othersw + " which does not exists");
			}

			// see if this pair is already connected
			if (already_connected[othersw + ":" + sw]) {
				continue;
			}

			var lport = this.switches[sw].freeport();
			var rport = this.switches[othersw].freeport();

			if (lport == -1) {
				throw new Error("Switch " + sw + " does not have free port" +
						"to connect to switch " + othersw);
			}
			if (rport == -1) {
				throw new Error("Switch " + othersw + " does not have free port" +
						"to connect to switch " + sw);
			}
			this.switches[sw].connectSwitch(
					lport,
					this.switches[othersw],
					rport);

			already_connected[sw + ":" + othersw] = true;
		}

		/** Read disks */
		for (var i = 0; i < conf.disks.length; i++) {
			var disk = conf.disks[i];

			if (!disk.name) {
				throw new Error("Name for disk not given");
			}
			this.disks[disk.name] = disk;
		}

		/** Read hosts */
		for (var i = 0; i < conf.hosts.length; i++) {
			this.addHost(conf.hosts[i]);
		}
	},

	addHost: function (conf) {
		var rack, sw, disk;

		if (!conf.name) {
			throw new Error("Name not given for host" + JSON.stringify(conf, null, 2));
		}

		if (this.hosts[conf.name]) {
			throw new Error("Another hosts exists by this name: " + name);
		}

		if (!conf.rack) {
			// this is ok only if there is only one rack
			if (Object.keys(this.racks).length != 1) {
				throw new Error("It is not clear to which rack the host " 
						+ conf.name + " connects to");
			}
			for (var prop in this.racks) {
				rack = this.racks[prop];
			}
		} else {
			if (!this.racks[conf.rack]) {
				throw new Error("Host " + conf.name + " cannot connect to rack "
						+ conf.rack + " which does not exist.");
			}
			rack = this.racks[conf.rack];
		}

		if (!conf.disk) {
			throw new Error("Configuration of disk not given for host " + conf.name);
		}
		if (!this.disks[conf.disk]) {
			throw new Error("Host " + conf.name + " cannot use disk "
					+ conf.disk + " which does not exist");
		}

		var host = this.sim.addEntity(Host, conf.name);
		host.ip = host.id; // ASSIGN IP Address
		this.hosts[conf.name] = host;

		if (conf.connect) {
			if (!this.switches[conf.connect]) {
				throw new Error("Host " + conf.name + " cannot connect to switch " 
						+ conf.connect + " which does not exists");
			}
			var sw = this.switches[conf.connect];
			var port = sw.freeport();
			if (port == -1) {
				throw new Error("Host " + conf.name + " cannot connect to switch " 
						+ conf.connect + ". The switch has no free port");
			}

			sw.connect(host, port);
			host.network = sw;
		}

		if (conf.apps) {
			for (var i = 0; i < conf.apps.length; i++) {
				host.addApplication(conf.apps[i]);
			}
		}

		host.name = conf.name;
		host.rack = rack;
		rack.hosts.push(host);
		host.disk = new Disk(this.disks[conf.disk], host);
		host.dc = this;
	},

	toJSON: function () {
		var conf = {};

		/** racks */
		conf.racks = [];
		for (var name in this.racks) {
			conf.racks.push({name: name});
		}

		/** switches */
		conf.switches = [];
		for (var name in this.switches) {
			var sw = this.switches[name];
			conf.switches.push({
				name: name,
				ports: sw.ports.length,
				speed: sw.speed
				});
		}

		/** disks */
		conf.disks = [];
		for (var name in this.disks) {
			conf.disks.push(this.disks[name]);
		}

		/** hosts */
		conf.hosts = [];
		for (var name in this.hosts) {
			var host = this.hosts[name];
			conf.hosts.push({
				name: name,
				disk: host.disk.name,
				rack: host.rack.name,
				connect: host.network.name,
				apps: Object.keys(host.apps)	
			});
		}

		return conf;
	},

	getHost: function (hostname) {
		if (!this.hosts) return null;
		return this.hosts[hostname];
	},

	getApplication: function (hostname, appname) {
		if (!this.hosts) return null;
		if (!this.hosts[hostname]) return null;
		return this.hosts[hostname].apps[appname];
	}
};


